package org.spring.boot.func.proxy;

import org.spring.boot.func.annotation.FuncConfiguration;
import org.spring.boot.func.annotation.FuncLambda;
import org.spring.boot.func.container.FuncMethod;
import org.spring.boot.func.proxy.handel.FuncBeanHandel;
import org.spring.boot.func.proxy.init.FuncInitBeanProxy;
import org.spring.boot.func.type.EnableFuncLambdaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;

import java.util.*;

/**
 * @author Yiur
 */
public class FuncRegistry implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor {

    /**
     * Logger
     */
    private final static Logger logger = LoggerFactory.getLogger(FuncRegistry.class);
    /**
     * Map<String,  List<FuncMethod>>
     */
    private Map<String, List<FuncMethod>> beans = new HashMap<>();
    /**
     * ApplicationContext
     */
    private ApplicationContext applicationContext;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        MutablePropertyValues enableProperty = registry.getBeanDefinition(FuncInitBeanProxy.class.getName()).getPropertyValues();
        FuncBeanHandel funcBeanHandel = new FuncBeanHandel(registry);
        provider.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

            Set<String> targets = annotationMetadata.getAnnotationTypes();
            String className = FuncConfiguration.class.getName();
            if (targets.contains(className)) {
                funcMethodInit(funcBeanHandel, annotationMetadata);
            }
            return false;
        });
        provider.findCandidateComponents((String) Objects.requireNonNull(enableProperty.get(EnableFuncLambdaType.PACKAGES.value)));
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beans.forEach((key, value) -> {
            BeanDefinition definition = beanFactory.getBeanDefinition(key);
            definition.getPropertyValues().addPropertyValue("beanFactory", beanFactory);
            definition.getPropertyValues().addPropertyValue("beanMethods", beans.get(key));
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private void funcMethodInit(FuncBeanHandel funcBeanHandel, AnnotationMetadata annotationMetadata) {
        Set<MethodMetadata> annotatedMethods = annotationMetadata.getAnnotatedMethods(FuncLambda.class.getName());
        annotatedMethods.forEach(methodMetadata -> {
            List<FuncMethod> funcMethods;
            String beanName = funcBeanHandel.resolve(methodMetadata, beans);

            if (beans.get(beanName) != null) {
                funcMethods = beans.get(beanName);
                funcMethods.add(new FuncMethod(methodMetadata, funcMethods));
                return;
            }

            funcMethods = new ArrayList<>();
            funcMethods.add(new FuncMethod(methodMetadata, funcMethods));
            beans.put(beanName, funcMethods);
        });
    }

}
